"""
ConnLLM配置管理
负责管理LLM配置，支持从多个位置加载
"""
import copy
import os
import json
import logging
from pathlib import Path
from typing import Dict, Any, Optional, List, Union

from .exceptions import ConfigurationError

# 设置日志
logger = logging.getLogger("connllm")

# 默认配置
DEFAULT_CONFIG = {
	"provider": "anthropic",      # 提供商类型
	"model": "claude-3-5-sonnet", # 模型名称
	"temperature": 0.1,           # 温度参数
	"max_tokens": 4096,           # 最大令牌数
	"max_retries": 3,             # 最大重试次数
	"retry_delay": 1.0,           # 重试延迟(秒)
	"proxy": None,                # 代理设置(例如: "http://127.0.0.1:7890")
	"app_name": "ConnLLM",        # 应用名称(用于OpenRouter等)
	"app_url": "https://github.com/your-username/ConnLLM"  # 应用URL
}

# 预定义配置模板
PREDEFINED_CONFIGS = {
	"anthropic": {
		"provider": "anthropic",
		"model": "claude-3-5-sonnet",
		"temperature": 0.1,
		"max_tokens": 4096
	},
	"openrouter_claude": {
		"provider": "openrouter",
		"model": "anthropic/claude-3.7-sonnet:thinking",
		"temperature": 0.1,
		"max_tokens": 4096,
		"app_name": "ConnLLM",
		"app_url": "https://github.com/your-username/ConnLLM"
	},
	"openai": {
		"provider": "openai",
		"model": "gpt-4o",
		"temperature": 0.1,
		"max_tokens": 4096,
		"proxy": "http://127.0.0.1:7890"  # 中国大陆需要代理
	},
	"openai_o1": {
		"provider": "openai",
		"model": "o1",
		"max_tokens": 4096,
		"proxy": "http://127.0.0.1:7890"  # 中国大陆需要代理
		# 注意：o1模型不支持temperature参数
	},
	"moonshot": {
		"provider": "moonshot",
		"model": "moonshot-v1-32k",
		"temperature": 0.1,
		"max_tokens": 4096
	},
	"moonshot_search": {
		"provider": "moonshot",
		"model": "moonshot-v1-32k",
		"temperature": 0.1,
		"max_tokens": 4096,
		"search": True  # 启用搜索功能
	},
	"ppinfra": {
		"provider": "ppinfra",
		"model": "deepseek/deepseek-v3",
		"temperature": 0.1,
		"max_tokens": 4096
	},
	"volces": {
		"provider": "volces",
		"model": "deepseek-v3",
		"temperature": 0.1,
		"max_tokens": 4096
	},
	"ollama": {
		"provider": "ollama",
		"model": "llama3.3:70b-instruct-q4_K_M",
		"temperature": 0.1,
		"max_tokens": 4096,
		"host": "http://localhost:11434"  # Ollama服务地址
	}
}


class ConfigManager:
	"""配置管理器，支持从多个位置加载配置"""
	
	def __init__(self, configs: Optional[Dict[str, Dict[str, Any]]] = None):
		"""
		初始化配置管理器
		
		Args:
			configs: 配置字典字典，键为配置名称，值为配置字典
		"""
		self.configs = copy.deepcopy(PREDEFINED_CONFIGS)
		
		# 尝试从多个位置加载配置
		user_configs = self._load_user_configs()
		if user_configs:
			self.configs.update(user_configs)
			
		# 使用传入的配置
		if configs:
			for name, config in configs.items():
				self.configs[name] = config
				
	def _load_user_configs(self) -> Dict[str, Dict[str, Any]]:
		"""
		从用户目录和项目目录加载配置文件
		
		Returns:
			配置字典
		"""
		configs = {}
		
		# 配置文件搜索路径
		search_paths = self._get_config_search_paths()
		
		for path in search_paths:
			config_file = path / "connllm_config.json"
			if config_file.exists():
				try:
					with open(config_file, "r", encoding="utf-8") as f:
						file_configs = json.load(f)
						# 验证配置格式
						if not isinstance(file_configs, dict):
							logger.warning(f"配置文件格式错误: {config_file}")
							continue
							
						for name, config in file_configs.items():
							if not isinstance(config, dict):
								logger.warning(f"配置 '{name}' 格式错误: {config_file}")
								continue
							configs[name] = config
							
					logger.debug(f"从 {config_file} 加载了配置")
				except (json.JSONDecodeError, IOError) as e:
					logger.warning(f"无法加载配置文件 {config_file}: {str(e)}")
		
		return configs
		
	def _get_config_search_paths(self) -> List[Path]:
		"""
		获取配置文件搜索路径
		
		Returns:
			Path列表
		"""
		paths = []
		
		# 当前工作目录
		paths.append(Path.cwd())
		
		# 用户配置目录
		if os.name == "nt":  # Windows
			app_data = os.environ.get("APPDATA")
			if app_data:
				paths.append(Path(app_data) / "ConnLLM")
		else:  # Linux/MacOS
			home = os.environ.get("HOME")
			if home:
				paths.append(Path(home) / ".config" / "connllm")
				
		# 项目目录 (如果作为包安装)
		try:
			import connllm
			package_dir = Path(connllm.__file__).parent
			paths.append(package_dir)
		except ImportError:
			pass
			
		# 创建不存在的目录
		for path in paths[1:]:  # 跳过当前工作目录
			if not path.exists():
				try:
					path.mkdir(parents=True, exist_ok=True)
				except Exception as e:
					logger.warning(f"无法创建配置目录 {path}: {str(e)}")
					
		return paths
		
	def get_config(self, name: str = "default", overrides: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
		"""
		获取指定名称的配置
		
		Args:
			name: 配置名称，如果为default则使用默认配置
			overrides: 覆盖配置的字典
			
		Returns:
			配置字典
		"""
		if name == "default":
			config = copy.deepcopy(DEFAULT_CONFIG)
		elif name in self.configs:
			config = copy.deepcopy(self.configs[name])
		else:
			# 尝试从环境变量加载配置
			env_config = self._load_from_env(name)
			if env_config:
				config = env_config
			else:
				raise ConfigurationError(f"未知配置: {name}")
			
		# 应用覆盖
		if overrides:
			self._update_dict(config, overrides)
		
		# 验证配置
		try:
			from ..schema import validate_config, get_provider_schema
			provider = config.get("provider")
			if provider:
				schema = get_provider_schema(provider)
				config = validate_config(config, schema)
		except ImportError:
			logger.warning("未能导入模式验证模块，跳过配置验证")
			
		return config
		
	def _load_from_env(self, name: str) -> Optional[Dict[str, Any]]:
		"""
		从环境变量加载配置
		
		Args:
			name: 配置名称
			
		Returns:
			配置字典或None
		"""
		prefix = f"CONNLLM_{name.upper()}_"
		config = {}
		
		for key, value in os.environ.items():
			if key.startswith(prefix):
				config_key = key[len(prefix):].lower()
				
				# 尝试转换值为适当的类型
				if value.lower() == "true":
					config[config_key] = True
				elif value.lower() == "false":
					config[config_key] = False
				elif value.lower() == "null" or value.lower() == "none":
					config[config_key] = None
				else:
					try:
						# 尝试转换为数字
						if "." in value:
							config[config_key] = float(value)
						else:
							config[config_key] = int(value)
					except ValueError:
						config[config_key] = value
		
		return config if config else None
		
	def set_config(self, name: str, config: Dict[str, Any]) -> None:
		"""
		设置配置
		
		Args:
			name: 配置名称
			config: 配置字典
		"""
		# 验证配置
		try:
			from ..schema import validate_config, get_provider_schema
			provider = config.get("provider")
			if provider:
				schema = get_provider_schema(provider)
				validate_config(config, schema)
		except ImportError:
			logger.warning("未能导入模式验证模块，跳过配置验证")
			
		self.configs[name] = copy.deepcopy(config)
		
		# 保存到用户配置文件
		self._save_user_config(name, config)
	
	def _save_user_config(self, name: str, config: Dict[str, Any]) -> bool:
		"""
		保存配置到用户配置文件
		
		Args:
			name: 配置名称
			config: 配置字典
			
		Returns:
			是否成功保存
		"""
		# 用户配置目录
		if os.name == "nt":  # Windows
			app_data = os.environ.get("APPDATA")
			if app_data:
				user_config_dir = Path(app_data) / "ConnLLM"
			else:
				return False
		else:  # Linux/MacOS
			home = os.environ.get("HOME")
			if home:
				user_config_dir = Path(home) / ".config" / "connllm"
			else:
				return False
				
		# 创建用户配置目录
		try:
			user_config_dir.mkdir(parents=True, exist_ok=True)
		except Exception as e:
			logger.warning(f"无法创建用户配置目录: {str(e)}")
			return False
			
		# 配置文件路径
		config_file = user_config_dir / "connllm_config.json"
		
		try:
			# 读取现有配置
			if config_file.exists():
				with open(config_file, "r", encoding="utf-8") as f:
					try:
						configs = json.load(f)
					except json.JSONDecodeError:
						configs = {}
			else:
				configs = {}
				
			# 更新配置
			configs[name] = config
			
			# 写入配置文件
			with open(config_file, "w", encoding="utf-8") as f:
				json.dump(configs, f, indent=4, ensure_ascii=False)
				
			logger.debug(f"配置 '{name}' 已保存到 {config_file}")
			return True
		except Exception as e:
			logger.warning(f"无法保存配置到文件 {config_file}: {str(e)}")
			return False
	
	def update_configs(self, configs: Dict[str, Dict[str, Any]]) -> None:
		"""
		批量更新配置
		
		Args:
			configs: 配置字典字典，键为配置名称，值为配置字典
		"""
		for name, config in configs.items():
			self.set_config(name, config)
			
	def remove_config(self, name: str) -> bool:
		"""
		移除指定名称的配置
		
		Args:
			name: 配置名称
			
		Returns:
			是否成功移除
		"""
		if name in self.configs:
			del self.configs[name]
			
			# 从用户配置文件中移除
			self._remove_user_config(name)
			return True
		return False
		
	def _remove_user_config(self, name: str) -> bool:
		"""
		从用户配置文件中移除配置
		
		Args:
			name: 配置名称
			
		Returns:
			是否成功移除
		"""
		# 用户配置目录
		if os.name == "nt":  # Windows
			app_data = os.environ.get("APPDATA")
			if app_data:
				user_config_dir = Path(app_data) / "ConnLLM"
			else:
				return False
		else:  # Linux/MacOS
			home = os.environ.get("HOME")
			if home:
				user_config_dir = Path(home) / ".config" / "connllm"
			else:
				return False
				
		# 配置文件路径
		config_file = user_config_dir / "connllm_config.json"
		
		if not config_file.exists():
			return False
			
		try:
			# 读取现有配置
			with open(config_file, "r", encoding="utf-8") as f:
				try:
					configs = json.load(f)
				except json.JSONDecodeError:
					return False
					
			# 移除配置
			if name in configs:
				del configs[name]
				
				# 写入配置文件
				with open(config_file, "w", encoding="utf-8") as f:
					json.dump(configs, f, indent=4, ensure_ascii=False)
					
				logger.debug(f"配置 '{name}' 已从 {config_file} 移除")
				return True
		except Exception as e:
			logger.warning(f"无法从文件 {config_file} 移除配置: {str(e)}")
			
		return False
		
	def get_predefined_config(self, name: str) -> Dict[str, Any]:
		"""
		获取预定义配置模板
		
		Args:
			name: 配置名称
			
		Returns:
			配置字典的副本
		"""
		if name in PREDEFINED_CONFIGS:
			return copy.deepcopy(PREDEFINED_CONFIGS[name])
		raise ConfigurationError(f"未知预定义配置: {name}")
		
	def list_configs(self) -> List[str]:
		"""
		获取所有可用的配置名称
		
		Returns:
			配置名称列表
		"""
		return list(self.configs.keys())
		
	def export_configs(self, path: Union[str, Path]) -> bool:
		"""
		导出所有配置到文件
		
		Args:
			path: 导出文件路径
			
		Returns:
			是否成功导出
		"""
		try:
			with open(path, "w", encoding="utf-8") as f:
				json.dump(self.configs, f, indent=4, ensure_ascii=False)
				
			logger.debug(f"所有配置已导出到 {path}")
			return True
		except Exception as e:
			logger.warning(f"无法导出配置到文件 {path}: {str(e)}")
			return False
			
	def import_configs(self, path: Union[str, Path]) -> bool:
		"""
		从文件导入配置
		
		Args:
			path: 导入文件路径
			
		Returns:
			是否成功导入
		"""
		try:
			with open(path, "r", encoding="utf-8") as f:
				configs = json.load(f)
				
			if not isinstance(configs, dict):
				logger.warning(f"配置文件格式错误: {path}")
				return False
				
			# 更新配置
			for name, config in configs.items():
				if not isinstance(config, dict):
					logger.warning(f"配置 '{name}' 格式错误: {path}")
					continue
				self.configs[name] = config
				
			logger.debug(f"从 {path} 导入了配置")
			return True
		except Exception as e:
			logger.warning(f"无法从文件 {path} 导入配置: {str(e)}")
			return False
		
	def _update_dict(self, d: Dict[str, Any], u: Dict[str, Any]) -> None:
		"""
		递归更新字典
		
		Args:
			d: 目标字典
			u: 源字典
		"""
		for k, v in u.items():
			if isinstance(v, dict) and k in d and isinstance(d[k], dict):
				self._update_dict(d[k], v)
			else:
				d[k] = v
